﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.EntityClient;
using System.Linq;
using System.Text;
using Fintech.EntityFrameworkEngine;
using Fintech.Utils;
using Fintech.Utils.Database;
using Fintech.Utils.Diagnostics;
using ZOVNP.Model.Abstracts;

namespace ZOVNP.Engine
{
    public class ZOVNPEngine : EFEngine, IDomainEngine
    {
        #region Поля

        /// <summary> Менеджер настроек. </summary>
        //private SettingsManager _settingsManager;
        /// <summary> Текущий пользователь движка. </summary>
		private IUser _user;
        private Lazy<Guid> _defaultOwnerId;
        /// <summary> Метаданные движка. </summary>
        private ZOVNPMetadata _metadata;
        /// <summary> Фабрика по генерации уникальных идентификаторов. </summary>
        private LazyLoader<GuidFactory> _guidFactory;
        /// <summary> Провайдер безопасности для базы Skif. </summary>
        private ZOVNPSecurityProvider _securityProvider;

        private EngineGetter _getter;
        private EngineAdder _creator;
        private EngineRemover _remover;
        /// <summary> Признак того, что движок уже был уничтожен. </summary>
        private volatile int _disposed;

		#endregion

        #region Конструкторы

        public ZOVNPEngine(DbProviderAdapter adapter, EntityConnection connection) : base(adapter, connection)
        {
            RestartDebug();
            _metadata = new ZOVNPMetadata(this);
            //_settingsManager = new SettingsManager(this);
            _guidFactory = new LazyLoader<GuidFactory>(() => new DotNetGuidFactory());

            _getter = new EngineGetter(this);
            _creator = new EngineAdder(this);
            _remover = new EngineRemover(this);
            _securityProvider = new ZOVNPSecurityProvider(this);

            using (var context = CreateContext())
            {
                _user = context.ExecuteProcedure<IUser>("GetCurrentUser").FirstOrDefault();
                if (_user == null)
                    throw new ApplicationException("Не удалось определить пользователя.");
            }
            DeveloperTest();
		}

        [System.Diagnostics.Conditional("DEBUG")]
        private void DeveloperTest()
        {
        }

	    #endregion

		#region События

        public event EventHandler CacheUpdated;

        private void OnCacheUpdated()
        {
            var temp = CacheUpdated;
            if (temp != null) temp(this, EventArgs.Empty);
        }

        public event PasswordChangingEventHandler PasswordChanging;

        protected virtual void OnPasswordChanging(ZOVNP.Model.Abstracts.PasswordChangingEventArgs e)
        {
            var temp = PasswordChanging;
            if (temp != null) temp(this, e);
        }

		#endregion

		#region Свойства

        //public ISettingsManager SettingsManager { get { return _settingsManager; } }

        IEngineGetter IDomainEngine.Getter { get { return _getter; } }

        internal EngineGetter Getter { get { return _getter; } }

        IEngineAdder IDomainEngine.Adder { get { return _creator; } }

        internal IEngineAdder Adder { get { return _creator; } }

        IEngineRemover IDomainEngine.Remover { get { return _remover; } }

        internal IEngineRemover Remover { get { return _remover; } }
        
        /// <summary> Метаданные движка. </summary>
        internal ZOVNPMetadata Metadata { get { return _metadata; } }
       
        /// <summary> Метаданные движка. </summary>
        IEngineMetadata IDomainEngine.Metadata { get { return _metadata; } }

        /// <summary> Поддерживает ли хранилище собственный механизм генерации уникальных идентификаторов. </summary>
        /// <value> True, если поддерживает, иначе false. </value>
        public bool HasOwnGuidFactory { get { return false; } }

        /// <summary> Сгенерировать уникальный идентификатор. </summary>
        /// <returns> Уникальный идентификатор. </returns>
        public Guid NewGuid() { return _guidFactory.Value.NewGuid(); }

        /// <summary> Получить текущую серверную дату. </summary>
        /// <returns> Текущая дата по серверному времени. </returns>
        public DateTime GetCurrentDateTime() { return ProviderAdapter.GetCurrentDateTime(); }

		/// <summary> Получить текущего пользователя. </summary>
        IUser IDomainEngine.CurrentUser { get { return _user; } }

        /// <summary> Получить текущего пользователя. </summary>
        public IUser CurrentUser { get { return _user; } }
        
	    /// <summary> Получить объект, предоставляющий методы, отвечающие за безопасность. </summary>
        public IZOVNPSecurityProvider SecurityProvider { get { return _securityProvider; } }
        
        #endregion

		#region Методы

        /// <summary> Создание вспомогательного соединения с источником данных. </summary>
        /// <returns> Созданное вспомогательное соединение с источником данных. </returns>
        public DbConnection CreateAuxiliaryConnection() { return ProviderAdapter.CreateConnection(EntityConnection.StoreConnection.ConnectionString); }

        /// <summary> Создание вспомогательного объекта для выполнения запросов к источнику данных. </summary>
        /// <param name="Connection"> Используемое соединение, null для использования текущего соединения. </param>
        /// <returns> Объект, реализующий интерфейс исполнения запросов. </returns>
        public IQueryExecutor CreateExecutor(DbConnection Connection) { return ProviderAdapter.CreateExecutor(Connection ?? EntityConnection.StoreConnection); }
        
        //TODO: !!!
        private class FiltersDataSource : TabularDataSource
        {
            private static SqlFieldDescriptor[] fields = new[] { new SqlFieldDescriptor("ID", SqlDbType.UniqueIdentifier, false) { PrimaryKey = true } };
            internal FiltersDataSource(IEnumerable<Guid> Filters) : base(Filters) { }
            public override object GetValue(DbFieldDescriptor Descriptor, int Index) { return Rows[Index]; }
            public override object[] GetValues(int Index) { return new object[] { Rows[Index] }; }
            public override DbFieldDescriptor[] GetFields() { return fields; }
        }
        
        public void Log(string OperationCode, string ObjectCode, Guid ObjectID, Guid? ParentObjectID = null, string Comment = null)
        {
            var p1 = ParameterProvider.CreateParam("Operation_Code", OperationCode, DbType.String);
            var p2 = ParameterProvider.CreateParam("Subject_ID", CurrentUser.ID);
            var p3 = ParameterProvider.CreateParam("Object_Code", ObjectCode, DbType.String);
            var p4 = ParameterProvider.CreateParam("Object_Instance_ID", ObjectID);
            var p5 = ParameterProvider.CreateParam("Parent_Object_Instance_ID", ParentObjectID, DbType.Guid);
            var p6 = ParameterProvider.CreateParam("Comment", Comment, DbType.String);
            ExecuteNonQuery("[global].[p_Log_Operation]", CommandType.StoredProcedure, p1, p2, p3, p4, p5, p6);
        }

        //TODO: !!!!! start + end логгеров надо перенести на этап запуска программы, иначе сейчас после dispose() движка управление сюда будет попадать и при завершении работы даже
        [System.Diagnostics.Conditional("DEBUG")]
        private static void RestartDebug()
        {
            try // на вебе этот код падает
            {
                GetDebugSettings().Apply();
            }
            catch (Exception) /* специально для вебовцев, ничего особенного не надо делать, просто задать параметры настройки явно */
            {
                DebugTuner.Instance = new DebugTuner(DebuggerSupportedLevel.Error, true, new OutputWriter(true));
            }
        }

        /// <summary> Получение настроек для отладки. </summary>
        /// <returns> Настройки отладки. </returns>
        private static DebugSettings GetDebugSettings()
        {
            DebugSettings settings = null;
            var setservice = (SettingsService)new ServiceLocatorHelper().ServiceLocator.GetService<ISettingsServiceProvider>();
            if (setservice != null)
            {
                settings = setservice.GetSettings<DebugSettings>();
                if (settings == null) // настройки запрашиваются впервые
                {
                    settings = new DebugSettings();
                    setservice.AddSettings(settings, typeof(DebugSettings));
                }
            }
            else settings = new DebugSettings();
            return settings;
        }

        /// <summary> Принятие положительного или отрицательного решения по некоторому вопросу. </summary>
        /// <param name="Message"> Текст вопроса. </param>
        /// <returns> true в случае положительного решения, иначе false. </returns>
        protected override bool ConfirmDecision(string Message)
        {
            return Services.Show(Message, MessageServiceMessageBoxButtons.OKCancel) == MessageServiceDialogResult.Ok;
        }

        /// <summary> Получение нового пароля. </summary>
        /// <param name="e"> Аргументы для установки нового пароля. </param>
        protected override void GetNewPassword(Fintech.EntityFrameworkEngine.PasswordChangingEventArgs e)
        {
            var args = new ZOVNP.Model.Abstracts.PasswordChangingEventArgs();
            OnPasswordChanging(args);
            e.Cancel = args.Cancel;
            e.OldPassword = args.OldPassword;
            e.NewPassword = args.NewPassword;
        }
        
        /// <summary> Создание контекста работы с доменными объектами. </summary>
		/// <param name="CommandTimeout"> Значение таймаута в секундах на выполнение запросов к хранилищу. Null для значения по умолчанию. </param>
		/// <returns> Контекст-песочница. </returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
		public IDomainContext CreateContext(int? CommandTimeout = null)
		{
            GuardDispose();
            var context = new ZOVNPContext(EntityConnection, this);
			if (CommandTimeout.HasValue) context.CommandTimeout = CommandTimeout;
            RegisterContext(context);
			return context;
		}
        
		/// <summary> Проверка существования доменных объектов в хранилище. </summary>
		/// <typeparam name="InterfaceType"> Тип проверяемых объектов. </typeparam>
		/// <param name="Keys"> Список идентификаторов проверяемых объектов. </param>
		/// <returns> Массив булевых значений, каждое из которых указывает, существует ли соответствующий объект в хранилище. </returns>
		public bool[] Exists<InterfaceType>(params Guid[] Keys) where InterfaceType : IDomainObject
		{
            GuardDispose();
			var t = typeof(InterfaceType);
			if (!t.IsInterface) throw new System.ArgumentException("Type должен быть интерфейсом.");
			Type implType = null;
            if (!ZOVNPMetadata.TypeMapping.TryGetForwardValue(t, out implType))
				throw new ServiceNotSupportedException(typeof(IObjectQuery<>).MakeGenericType(t));
			var tuple = Metadata.GetSchemaAndTable(implType);
			var sb = new System.Text.StringBuilder("declare @ID_List as table ([ID] uniqueidentifier not null)");
			sb.AppendLine();
			foreach (var key in Keys)
				sb.Append("insert into @ID_List (ID) values ('").Append(key.ToString()).AppendLine("')");
			sb.Append("select [A].[ID], [B].[ID] AS [ID1] from @ID_List as [A] left join ").
                Append(tuple.Item1.EnsureSquareBrackets()).Append(".").Append(tuple.Item2.EnsureSquareBrackets()).Append(" as [B] on [B].[ID] = [A].[ID]");
			var result = ExecuteReader<Pair<Guid, bool>>(sb.ToString(), CommandType.Text, rec => new Pair<Guid, bool>(rec.GetGuid(0), rec.GetValue(1) != DBNull.Value));
			return Keys.Select(it => result.First(r => r.Item1 == it).Item2).ToArray();
		}

		/// <summary> Проверка существования доменных объектов в хранилище. </summary>
		/// <typeparam name="InterfaceType"> Тип проверяемых объектов. </typeparam>
		/// <param name="Items"> Список проверяемых объектов. </param>
		/// <returns> Массив булевых значений, каждое из которых указывает, существует ли соответствующий объект в хранилище. </returns>
		public bool[] Exists<InterfaceType>(params InterfaceType[] Items) where InterfaceType : IDomainObject
		{
            GuardDispose();
			Items.CheckOnNull("Items");
			if (Items.Length == 0) return new bool[0];
            if (Items.Any(it => it == null)) throw new ArgumentException(Properties.Resources.MSG_CollectionShouldNotContainNULL);
			return Exists<InterfaceType>(Items.Select(it => it.ID).ToArray());
		}

		/// <summary> Проверка существования всех заданных доменных объектов в хранилище. </summary>
		/// <typeparam name="InterfaceType"> Тип проверяемых объектов. </typeparam>
        /// <param name="Items"> Список проверяемых объектов. </param>
		/// <returns> True, если все указанные в списке объекты существуют в хранилище, иначе false. </returns>
		public bool ExistsAll<InterfaceType>(params InterfaceType[] Items) where InterfaceType : IDomainObject
		{
            GuardDispose();
			Items.CheckOnNull("Items");
            if (Items.Length == 0) return false;
            if (Items.Any(it => it == null)) throw new ArgumentException(Properties.Resources.MSG_CollectionShouldNotContainNULL);
			return ExistsAll<InterfaceType>(Items.Select(it => it.ID).ToArray());
		}

		/// <summary> Проверка существования всех заданных доменных объектов в хранилище. </summary>
		/// <typeparam name="InterfaceType"> Тип проверяемых объектов. </typeparam>
		/// <param name="Keys"> Список идентификаторов проверяемых объектов. </param>
		/// <returns> True, если все указанные в списке объекты существуют в хранилище, иначе false. </returns>
		public bool ExistsAll<InterfaceType>(params Guid[] Keys) where InterfaceType : IDomainObject
		{
            GuardDispose();
			var t = typeof(InterfaceType);
			if (!t.IsInterface) throw new System.ArgumentException("Type должен быть интерфейсом.");
			Type implType = null;
            if (!ZOVNPMetadata.TypeMapping.TryGetForwardValue(t, out implType))
				throw new ServiceNotSupportedException(typeof(IObjectQuery<>).MakeGenericType(t));
			var tuple = Metadata.GetSchemaAndTable(implType);
			var sb = new System.Text.StringBuilder("select count(*) from ");
            sb.Append(tuple.Item1.EnsureSquareBrackets()).Append(".").Append(tuple.Item2.EnsureSquareBrackets()).Append(" where [ID] in (");
			foreach (var id in Keys)
				sb.Append("'").Append(id.ToString()).Append("', ");
			sb.Length -= 2;
			sb.Append(")");
			return (int)ExecuteScalar(sb.ToString(), CommandType.Text) == Keys.Length;
		}

        ///// <summary> Признак поддержки сервиса получения объектов заданного типа. </summary>
        ///// <typeparam name="T"> Тип объектов. </typeparam>
        ///// <returns> True, если сервис поддерживается, иначе false. </returns>
        //public override bool SupportsQuery<T>()
        //{ 
        //    var InterfaceType = typeof(T);
        //    if (!InterfaceType.IsInterface) return false;
        //    Type implType = null;
        //    if (!ZOVNPMetadata.TypeMapping.TryGetForwardValue(InterfaceType, out implType)) return false;
        //    var prop = typeof(ZOVNPContext).GetProperty(InterfaceType.Name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
        //    return prop != null;
        //}
		
		/// <summary> Проверить пригодность значения свойства для заданного типа доменного объекта. </summary>
		/// <param name="InterfaceType"> Интерфейсный тип доменного объекта. </param>
		/// <param name="PropertyName"> Имя свойства, для которого делается проверка значения. </param>
		/// <param name="Value"> Проверяемое значение свойства. </param>
		/// <param name="Context"> Контекст, доступный для метода проверки. </param>
        /// <param name="Explanation"> Поясняющее сообщение об ошибке, если метод вернул false. </param>
		/// <returns> True в случае успешности проверки, иначе false. </returns>
		public bool CheckPropertyValue(Type InterfaceType, string PropertyName, object Value, object Context, out string Explanation)
		{
			if (!InterfaceType.IsInterface) throw new System.ArgumentException("Параметр должен задавать интерфейс.", "InterfaceType");
			Type implType = null;
            if (!ZOVNPMetadata.TypeMapping.TryGetForwardValue(InterfaceType, out implType))
				throw new System.NotSupportedException(string.Format("Не найден класс, реализующий интерфейс {0}.", InterfaceType.FullName));
			var property = implType.GetProperty("PropertyValueChecker", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
            if (property == null) throw new System.NotSupportedException(string.Format(Properties.Resources.MSG_PropertyValueCheckerNotFound, implType));
			return ((IPropertyValueChecker)property.GetValue(null, null)).CheckProperty(PropertyName, Value, Context, out Explanation);
		}

        /// <summary> Уничтожить движок. </summary>
        /// <param name="Disposing"> true to release both managed and unmanaged resources; false to release only unmanaged resources. </param>
        protected override void Dispose(bool Disposing)
        {
            #pragma warning disable 420
            if (!Disposing || System.Threading.Interlocked.CompareExchange(ref _disposed, 1, 0) != 0) return;
            #pragma warning restore 420

            base.Dispose(Disposing);
            RestartDebug(); //TODO: перенести
        }

		#endregion


        public ISettingsManager SettingsManager
        {
            get { throw new NotImplementedException(); }
        }

        public event EventHandler<ProgressStartedEventArgs> ProgressStarted;

        public event EventHandler ProgressFinished;

        public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
    }
}
